package com.smsc.headend.task.engine.handler.impl.collection;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.SystemClock;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.emeter.energyip.amiinterface.MeterReadsReplyMessageDocument;
import com.smsc.headend.common.enums.DeviceType;
import com.smsc.headend.common.enums.errorcode.TaskHandlerErrorCode;
import com.smsc.headend.common.utils.CosemUtils;
import com.smsc.headend.common.utils.ProtoBufferUtil;
import com.smsc.headend.common.utils.RedisKeys;
import com.smsc.headend.common.utils.RedisUtils;
import com.smsc.headend.module.asset.entity.Meter;
import com.smsc.headend.module.data.dmo.GroupSubDataItem;
import com.smsc.headend.module.data.dto.AnsiDataDTO;
import com.smsc.headend.module.data.dto.DataItemDTO;
import com.smsc.headend.module.data.dto.TaskDataDTO;
import com.smsc.headend.module.data.dto.TaskPacketDTO;
import com.smsc.headend.module.event.entity.RdEventMeter;
import com.smsc.headend.module.event.entity.RdEventMeterData;
import com.smsc.headend.module.protocol.DeviceData;
import com.smsc.headend.module.protocol.ansi.AnsiByteBuffer;
import com.smsc.headend.module.protocol.ansi.AnsiData;
import com.smsc.headend.module.protocol.cosem.bo.CosemData;
import com.smsc.headend.module.report.entity.LogScheduleTaskDevice;
import com.smsc.headend.module.task.consts.TaskKafkaTopic;
import com.smsc.headend.module.task.dto.*;
import com.smsc.headend.module.task.enums.TaskType;
import com.smsc.headend.module.task.proto.TaskEventDataProto;
import com.smsc.headend.task.engine.dao.TaskDAO;
import com.smsc.headend.task.engine.exception.UDISTaskHandlerException;
import com.smsc.headend.task.engine.handler.TaskHandler;
import com.smsc.headend.task.engine.service.CollMeterTaskPlanService;
import com.smsc.headend.task.engine.service.CosemXmlService;
import com.smsc.headend.task.engine.service.DataProcessService;
import com.smsc.headend.task.engine.service.FeignAssetManagementService;
import com.smsc.headend.task.engine.service.asset.AssetService;
import com.smsc.headend.task.engine.service.push.EventService;
import com.smsc.headend.task.engine.service.task.LogScheduleTaskDeviceService;
import com.smsc.headend.task.engine.service.task.impl.TaskServiceImpl;
import com.smsc.headend.task.engine.utils.DdsXmlGenerateUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.util.CollectionUtils;

import java.nio.charset.Charset;
import java.util.*;

/**
 * @author liangli
 * @date 2020/11/12
 */
@Slf4j
public class ReadEventLogBufferHandler implements TaskHandler {
    @Autowired
    FeignAssetManagementService assetManagementService;
    @Autowired
    AssetService assetService;
    @Autowired
    CosemXmlService cosemXmlService;
    @Autowired
    KafkaTemplate kafkaTemplate;
    @Autowired
    EventService eventService;
    @Autowired
    DataProcessService dataProcessService;
    @Autowired
    TaskDAO taskDAO;
    @Autowired
    TaskServiceImpl taskService;
    @Autowired
    CollMeterTaskPlanService collMeterTaskPlanService;
    @Autowired
    DdsXmlGenerateUtil ddsXmlGenerateUtil;
    @Autowired
    LogScheduleTaskDeviceService logScheduleTaskDeviceService;
    @Autowired
    RedisUtils redisUtils;

    @Override
    public int handle(Task task, AtomicTask atomicTask, DeviceData deviceData, String actionResult) throws UDISTaskHandlerException {
        log.debug("{}", deviceData.toString());
        if (!(deviceData instanceof CosemData) && !(deviceData instanceof AnsiData)) {
            throw new UDISTaskHandlerException(TaskHandlerErrorCode.DEVICE_DATA_TYPE_NOT_SUPPORT.toString());
        }

        Meter meter = assetService.getMeterById(task.getMeterId());
        List<RdEventMeter> allEvent = new ArrayList<>();
        Map<String, Object> param = JSONUtil.toBean(atomicTask.getAtomicTaskParam(), Map.class);
        Long lbDataItemId = Long.valueOf(String.valueOf(param.get("dataItemId")));
        Long lbTemplateId = param.get("lbTemplateId") != null ? Long.valueOf(String.valueOf(param.get("lbTemplateId"))) : null;
        if (lbTemplateId == null) {
            throw new UDISTaskHandlerException(TaskHandlerErrorCode.NO_LB_TEMPLATE_ID.toString());
        }
        if(deviceData instanceof AnsiData) {
            String dataBufferKey = RedisKeys.getAnsiDataBufferKey(task.getTaskNo());
            int backAnsiData = callBackAnsiData(task, atomicTask, (AnsiData) deviceData, dataBufferKey, lbDataItemId);
            // completed, get the stored data
            if(backAnsiData == 1) {
                Object data;
                while ((data = redisUtils.lpop(dataBufferKey)) != null) {
                    RdEventMeter eventMeter = JSONUtil.toBean(String.valueOf(data), RdEventMeter.class);
                    allEvent.add(eventMeter);
                }

                handleEvent(task, atomicTask, meter, allEvent, lbDataItemId);
            }
            return backAnsiData;
        } else {
            callBackCosemData(task, (CosemData) deviceData, allEvent, param, lbDataItemId, lbTemplateId);
            handleEvent(task, atomicTask, meter, allEvent, lbDataItemId);

            return 1;
        }
    }

    private void callBackCosemData(Task task, CosemData deviceData, List<RdEventMeter> allEvent, Map<String, Object> param, Long lbDataItemId, Long lbTemplateId) throws UDISTaskHandlerException {
        CosemData cosemData = deviceData;
        Long tvUpdate = SystemClock.now() / 1000;
        if (!CollectionUtils.isEmpty(cosemData.getChild())) {
            List<DataItemDTO> dataItemCosemList = assetManagementService.getEventProfileDataItemForDevice(task.getMeterId(), lbTemplateId, DeviceType.METER.getId());
            int clockSeq = dataProcessService.getClockIndexInConfigDataItem(dataItemCosemList);
            int eventCodeSeq = getEventIndexInConfigDataItem(dataItemCosemList);
            int finalClockSeq = clockSeq;
            log.debug("clock {} code seq {} length of struct {}", finalClockSeq, eventCodeSeq, cosemData.getChild().size());
            if (clockSeq < 0 || eventCodeSeq < 0) {
                log.error("config error, no clock or event Code meterId={}, comId={},dataItems={}", task.getMeterId(), task.getComId(), dataItemCosemList);
                throw new UDISTaskHandlerException(TaskHandlerErrorCode.INVALID_TEMPLATE_CONFIG.toString());
//                return -1;
            }
            cosemData.getChild().stream().forEach(structData -> {
                int index = 0;
                String timeString = null;
                Integer eventCode = null;
                RdEventMeter rdEventMeter = new RdEventMeter();
                rdEventMeter.setMeterId(task.getMeterId());
                rdEventMeter.setLbDataItemId(lbDataItemId);
                if (param.containsKey("alarmLogSeq")) {
                    rdEventMeter.setAlarmLogSeq(MapUtil.getStr(param, "alarmLogSeq"));
                }
                List<RdEventMeterData> eventDataList = new ArrayList<>();
                for (CosemData data : structData.getChild()) {
                    if (index == finalClockSeq) {
                        Object dValue = cosemXmlService.getData(data.getType(), data.getValue());
                        if (dValue instanceof Date) {
                            timeString = data.getValue();
                        } else {
                            timeString = Convert.toStr(cosemXmlService.getData(data.getType(), data.getValue()));
                        }
                        Date date = CosemUtils.parseOctStringDataTime(timeString);
                        rdEventMeter.setTv(date.getTime() / 1000);
                        index++;
                        continue;
                    }

                    if (index == eventCodeSeq) {
                        eventCode = Convert.toInt(cosemXmlService.getData(data.getType(), data.getValue()));
                        Integer eventType = eventService.getTaskEventType(task, eventCode, lbDataItemId, DeviceType.METER.getId());
                        rdEventMeter.setEventType(eventType);
                        rdEventMeter.setEventCode(eventCode);
                        if (null != eventType) {
                            rdEventMeter.setEventName(eventService.getEventName(Convert.toLong(eventType)));
                        } else {
                            rdEventMeter.setEventName("not config");
                            log.error("not found the eventType in db：eventCode={},lbDataItemId={},deviceType={},comId={}, meterId={}", eventCode, lbDataItemId, DeviceType.METER.getId(), task.getComId(), task.getMeterId());
                        }
                        index++;
                        continue;
                    }

                    if (index >= dataItemCosemList.size()) {
                        log.warn("profile object list longer than list configured in UDIS");
                        break;
                    }
                    RdEventMeterData rdEventMeterData = new RdEventMeterData();
                    DataItemDTO dataItemCosem = dataItemCosemList.get(index);
                    rdEventMeterData.setSubDataItemId(dataItemCosem.getSubDataItemId());
                    rdEventMeterData.setValue(String.valueOf(cosemXmlService.getData(data.getType(), data.getValue())));
                    eventDataList.add(rdEventMeterData);
                    index++;
                }
                rdEventMeter.setSubData(eventDataList);
                rdEventMeter.setReceiveType(2);
                rdEventMeter.setTvUpdate(tvUpdate);
                allEvent.add(rdEventMeter);
            });
        }
    }

    private void handleEvent(Task task, AtomicTask atomicTask, Meter meter, List<RdEventMeter> allEvent, Long lbDataItemId) {
        TaskEventData readData = new TaskEventData();
        readData.setRdEventMeter(allEvent);
        readData.setLbDataItemId(lbDataItemId);
        readData.setTaskNo(task.getTaskNo());
        readData.setIsODR(task.getIsODR());
        readData.setDeviceId(task.getMeterId());
        readData.setDeviceType(DeviceType.METER.getId());
        if (allEvent.size() > 0) {
            kafkaTemplate.send(TaskKafkaTopic.DEVICE_EVENT_TRANSFER, ProtoBufferUtil.toProtoBuffer(readData, TaskEventDataProto.TaskEventData.class).toByteArray());
            if (TaskType.EventRead_Profile.toString().equalsIgnoreCase(task.getTaskType())) {
                logScheduleTaskDeviceService.sendMessage(task, LogScheduleTaskDevice.StatusEnum.Success.getCode(), LogScheduleTaskDevice.StatusDescEnum.CollectedSuccess.getCode());
            }
        } else {
            if (TaskType.EventRead_Profile.toString().equalsIgnoreCase(task.getTaskType())) {
                logScheduleTaskDeviceService.sendMessage(task, LogScheduleTaskDevice.StatusEnum.Failed.getCode(), LogScheduleTaskDevice.StatusDescEnum.EmptyData.getCode());
            }
        }
//        eventService.saveLogbookReadResult(readData, task.getMeterId());
        if (task.getIsODR()) {
            taskDAO.addTaskData(task.getTaskNo(), atomicTask.getAtomicTaskNo(), lbDataItemId.toString(), JSONUtil.toJsonStr(allEvent), Long.valueOf(1 * 60 * 60), true);
        }
        if (TaskType.EventRead_Profile.toString().equalsIgnoreCase(task.getTaskType())) {
            collMeterTaskPlanService.saveMeterCollectProgress(SystemClock.now() / 1000, task.getMeterId(), lbDataItemId);
        }
        if (StrUtil.equalsIgnoreCase(task.getTaskType(), TaskType.Interface_ODR_LogBook.name()) || StrUtil.equalsIgnoreCase(task.getTaskType(), TaskType.UAA_MeterReads.name())) {
            taskDAO.addTaskData(task.getTaskNo(), atomicTask.getAtomicTaskNo(), lbDataItemId.toString(), allEvent.size() > 0 ? JSONUtil.toJsonStr(allEvent) : "no data", Long.valueOf(1 * 60 * 60), true);
            // UAA接口的随抄执行成功要返回给 WF MDM
            List<TaskPacketDTO> taskPacketDTOList = taskService.getTaskPacket(task.getTaskNo());
//            if (CollectionUtil.isNotEmpty(taskPacketDTOList)){
            log.debug("Interface_ODR_LogBook packet result are: {}", JSONUtil.toJsonStr(taskPacketDTOList));


            TaskResult taskResult = new TaskResult();
            taskResult.setMessageId(task.getMessageId());
            taskResult.setTaskNo(task.getTaskNo());
            taskResult.setResultData(readData);

            Map<String, Object> dataItemMap = JSONUtil.toBean(atomicTask.getAtomicTaskParam(), Map.class);
            Map<String, Object> taskResultMap = JSONUtil.toBean(task.getParamJson(), Map.class);
            taskResultMap.putAll(dataItemMap);

            taskResultMap.put("packets", taskPacketDTOList);
            taskResultMap.put("meterModelId", meter.getModelId());
            taskResultMap.put("meterCustomSn", meter.getCustomerSn());
            taskResultMap.put("profileDataItemId", lbDataItemId);
            taskResult.setParamJson(JSONUtil.toJsonStr(taskResultMap));
            if (isTaskLastAtomicTask(task, atomicTask)) {
                List<TaskDataDTO> taskDataDTOS = taskDAO.getAllTaskData(task.getTaskNo());
                List<RdEventMeter> currentRdEventMeters = new ArrayList<>();
                for (TaskDataDTO taskDataDTO : taskDataDTOS) {
                    Object value = taskDataDTO.getValue();
                    String rdStr = (String) value;
                    if (!"no data".equals(rdStr)) {
                        List<RdEventMeter> rdEventMeters = JSONUtil.toList(rdStr, RdEventMeter.class);
                        currentRdEventMeters.addAll(rdEventMeters);
                    }
                }
                MeterReadsReplyMessageDocument document = null;
                if (CollectionUtil.isNotEmpty(currentRdEventMeters)) {
                    document = ddsXmlGenerateUtil.getDocumentEvent(Arrays.asList(readData));
                } else {
                    document = MeterReadsReplyMessageDocument.Factory.newInstance();
                }
                taskResult.setDocumentData(document.toString());
            }
            kafkaTemplate.send(TaskKafkaTopic.TASK_COMPLETE_RESULT_FROM_UTE_TO_ADAPTER, JSONUtil.toJsonStr(taskResult).getBytes(Charset.forName("utf-8")));
//            } else {
//                log.error("odr logBook Profile Data reply without packets! taskNo: {}", task.getTaskNo());
//            }
        }
    }

    private int callBackAnsiData(Task task, AtomicTask atomicTask, AnsiData deviceData, String dataBufferKey, Long lbDataItemId) {
        AnsiData ansiData = deviceData;
        AtomicTaskState atomicTaskState = taskDAO.hashGetAtomicTaskState(atomicTask.getAtomicTaskNo());
        AnsiByteBuffer dataBuffer = ansiData.getAnsiByteBuffer();
        // first read, get the Event Log Status
        if(!atomicTaskState.getData().containsKey("NBR_VALID_ENTRIES")) {
            log.info("get Event Log Status {}", ansiData.getAnsiByteBuffer());
            dataBuffer.position(4);
            int NBR_VALID_ENTRIES = dataBuffer.getUInt16();
            int LAST_ENTRY_ELEMENT = dataBuffer.getUInt16();
            long LAST_ENTRY_SEQ_NBR = dataBuffer.getUInt32();
            int NBR_UNREAD_ENTRIES = dataBuffer.getUInt16();
            atomicTaskState.getData().put("NBR_VALID_ENTRIES", NBR_VALID_ENTRIES);
            atomicTaskState.getData().put("LAST_ENTRY_ELEMENT", LAST_ENTRY_ELEMENT);
            atomicTaskState.getData().put("LAST_ENTRY_SEQ_NBR", LAST_ENTRY_SEQ_NBR);
            atomicTaskState.getData().put("NBR_UNREAD_ENTRIES", NBR_UNREAD_ENTRIES);
            taskDAO.hashSetAtomicTaskState(atomicTask.getAtomicTaskNo(), task, atomicTaskState.getRetryTimes(), atomicTaskState);

            return 0;
        }

        // read the event
        // event time
        dataBuffer.position(3);
        Long eventTime = dataBuffer.getDateTime();
        Long profileStartTime = MapUtil.getLong(atomicTaskState.getData(), "profileStartTime");
        Long profileEndTime = MapUtil.getLong(atomicTaskState.getData(), "profileEndTime");
        Long currentOffSet = MapUtil.getLong(atomicTaskState.getData(), "CURRENT_OFF_SET");
        int lastEntryElement = MapUtil.getInt(atomicTaskState.getData(), "LAST_ENTRY_ELEMENT");
        if(eventTime < profileStartTime || (currentOffSet == (lastEntryElement * 28 + 11) && atomicTaskState.isStored())) {

            return 1;
        } else {
            if(eventTime >= profileStartTime && eventTime <= profileEndTime) {
                int seqNumber = dataBuffer.getUInt16();
                int userId = dataBuffer.getUInt16();
                int eventInfo = dataBuffer.getUInt16();
                int eventCode = eventInfo & 0x7FF;
                // 0:std 1:mfg
                int stdOrMfg = (eventInfo & 0x800) >> 11;
                if(stdOrMfg == 0) {
                    log.info("get the event {}, at {}", eventCode, DateUtil.formatDateTime(new Date(eventTime * 1000)));
                    RdEventMeter rdEventMeter = new RdEventMeter();
                    rdEventMeter.setMeterId(task.getMeterId());
                    rdEventMeter.setLbDataItemId(lbDataItemId);
                    rdEventMeter.setTv(eventTime);
                    Integer eventType = eventService.getTaskEventType(task, eventCode, lbDataItemId, DeviceType.METER.getId());
                    rdEventMeter.setEventType(eventType);
                    rdEventMeter.setEventCode(eventCode);
                    if (null != eventType) {
                        rdEventMeter.setEventName(eventService.getEventName(Convert.toLong(eventType)));
                    } else {
                        rdEventMeter.setEventName("not config");
                        log.error("not found the eventType in db：eventCode={},lbDataItemId={},deviceType={},comId={}, meterId={}", eventCode, lbDataItemId, DeviceType.METER.getId(), task.getComId(), task.getMeterId());
                    }
                    rdEventMeter.setReceiveType(2);
                    rdEventMeter.setTvUpdate(SystemClock.now() / 1000);
                    redisUtils.lpush(dataBufferKey, JSONUtil.toJsonStr(rdEventMeter));
                }
            }

            return 0;
        }
    }

    private int getEventIndexInConfigDataItem(List<DataItemDTO> dataItemCosemList) {
        int eventSeq = -1;
        if (CollectionUtils.isEmpty(dataItemCosemList)) {
            return -1;
        }
        for (DataItemDTO modelDataItemCosem : dataItemCosemList) {
            eventSeq++;
            if (modelDataItemCosem.getDataGroupIds().contains(GroupSubDataItem.SpecialDataGroup.EVENT_CODE.getCode())) {
                break;
            }
        }
        if (eventSeq >= dataItemCosemList.size()) {
            return -1;
        }
        return eventSeq;
    }

    @Override
    public int handleFailure(Task task, AtomicTask atomicTask, String message) {
        log.info("error {}, ", message);
        if (task.getIsODR()) {
            Map<String, Object> param = getParameterMap(task, atomicTask);
            Long lbDataItemId = Long.valueOf(String.valueOf(param.get("dataItemId")));
            // key 回曲线的dataItemId给web
            taskDAO.addTaskData(task.getTaskNo(), atomicTask.getAtomicTaskNo(), Convert.toStr(lbDataItemId), message, 3600l, false);
        }
        if (StrUtil.equalsIgnoreCase(task.getTaskType(), TaskType.Interface_ODR_LogBook.name()) || StrUtil.equalsIgnoreCase(task.getTaskType(), TaskType.UAA_MeterReads.name())) {
            // UAA接口的随抄执行失败也要返回给 WF MDM
            TaskResult taskResult = new TaskResult();
            taskResult.setMessageId(task.getMessageId());
            taskResult.setTaskNo(task.getTaskNo());
            taskResult.setExceptionCause(message);
            Map<String, Object> dataItemMap = getParameterMap(task, atomicTask);
            Map<String, Object> taskResultMap = JSONUtil.toBean(task.getParamJson(), Map.class);
            taskResultMap.putAll(dataItemMap);
            Meter meter = assetService.getMeterById(task.getMeterId());
            Long lbDataItemId = Long.valueOf(String.valueOf(dataItemMap.get("dataItemId")));
            List<TaskPacketDTO> taskPacketDTOList = taskService.getTaskPacket(task.getTaskNo());
            if (CollectionUtil.isNotEmpty(taskPacketDTOList)) {
                taskResultMap.put("packets", taskPacketDTOList);
            }
            taskResultMap.put("meterModelId", meter.getModelId());
            taskResultMap.put("meterCustomSn", meter.getCustomerSn());
            taskResultMap.put("profileDataItemId", lbDataItemId);
            taskResult.setExceptionCause(message);
            taskResult.setParamJson(JSONUtil.toJsonStr(taskResultMap));
            kafkaTemplate.send(TaskKafkaTopic.TASK_COMPLETE_RESULT_FROM_UTE_TO_ADAPTER, JSONUtil.toJsonStr(taskResult).getBytes(Charset.forName("utf-8")));
        }
        if (StrUtil.equalsIgnoreCase(task.getTaskType(), TaskType.EventRead_Profile.name())) {
            logScheduleTaskDeviceService.sendMessage(task, LogScheduleTaskDevice.StatusEnum.Failed.getCode(), LogScheduleTaskDevice.StatusDescEnum.DecodeDataFailed.getCode());
        }
        return -1;
    }
}
